/*
 * Balancer.cpp
 *
 *  Created on: 28/06/2014
 *      Author: Caina
 */

#include "Balancer.h"

Balancer::Balancer() {
}

Balancer::~Balancer() {
}

Balancer& Balancer::get_instance() {
	static Balancer instance;

	return instance;
}

unsigned int Balancer::get_manager_ip() {

	unsigned int manager_ip = 0;

	Globals::peers.lock();
	for (auto it = Globals::peers.get().begin();
			it != Globals::peers.get().end(); it++) {
		if (it->first > manager_ip) {
			manager_ip = it->first;
		}
	}
	Globals::peers.unlock();

	return manager_ip;

}

std::string Balancer::convert_ip_to_string(unsigned int ip) {
	unsigned int bit24 = 0xFF000000;
	unsigned int bit16 = 0x00FF0000;
	unsigned int bit8 = 0x0000FF00;
	unsigned int bit0 = 0x000000FF;
	char ipAddr[20];
	sprintf(ipAddr, "%u.%u.%u.%u", (ip & bit0), (ip & bit8) >> 8,
			(ip & bit16) >> 16, (ip & bit24) >> 24);
	return std::string(ipAddr);
}

void Balancer::send_message(Order order) {

	TCPSocket socket(convert_ip_to_string(order.get_dstIp()).c_str(), 7777);

	socket.open();
	socket.send(order.get_message());
	socket.close();

}

int Balancer::get_average() {
	int average;

	Globals::peers.lock();
	Globals::file_table.lock();

	average = floor(
			2 * Globals::file_table.get()->get_file_table().size()
					/ Globals::peers.get().size());

	Globals::file_table.unlock();
	Globals::peers.unlock();

	return average;
}

void Balancer::broadcast_to_peers(const std::vector<char>& message,
		bool including_myself) {
	Globals::peers.lock();
	for (auto it = Globals::peers.get().begin();
			it != Globals::peers.get().end(); it++) {
		if (!including_myself
				&& it->first == Globals::peer.get().get_ip_address()) {
			continue;
		}
		auto ip = convert_ip_to_string(it->first);
		TCPSocket socket(ip.c_str(), 7777);

		printf("Mandei Mensagem %d para %x\n",(int)message[0],it->first);

		socket.open();
		socket.send(message);
		socket.close();
	}
	Globals::peers.unlock();
}

void Balancer::send_updated_file_table() {

	Globals::file_table.lock();
	FileTable* file_table = Globals::file_table.get();
	auto queue = file_table->serialize();
	Globals::file_table.unlock();

	queue.push_front<unsigned int>(queue.size());
	queue.push_front<char>(MessageTypes::FILE_TABLE);

	Globals::peers.lock();
	for (auto it = Globals::peers.get().begin();
			it != Globals::peers.get().end(); it++) {
		send_message(Order(it->first, queue.get_raw()));
	}
	Globals::peers.unlock();
}

void Balancer::update_file_count() {

	file_count = std::map<unsigned int, int>();

	Globals::peers.lock();
	for (auto it = Globals::peers.get().begin();
			it != Globals::peers.get().end(); it++) {
		file_count[it->first] = 0;
	}
	Globals::peers.unlock();

	Globals::file_table.lock();
	Globals::file_table.get()->get_file_count(file_count);
	Globals::file_table.unlock();
}

void Balancer::print_file_count() {
	for (auto it = file_count.begin(); it != file_count.end(); it++) {
		printf("\n\nPeer %x possui %d arquivos\n\n", it->first, it->second);
	}
}

void Balancer::new_node() {

	int i, average;
	unsigned int top_peer, bottom_peer;
	std::string file_name;
	ByteQueue message, peers_message, user_table_message;

	Globals::peers.lock();
	peers_message = Globals::serialize_peers();
	Globals::peers.unlock();

	peers_message.push_front<unsigned int>(peers_message.size());
	peers_message.push_front<char>(MessageTypes::PEERS);

	Globals::user_table.lock();
	user_table_message = Globals::serialize_user_table();
	Globals::user_table.unlock();

	user_table_message.push_front<unsigned int>(user_table_message.size());
	user_table_message.push_front<char>(MessageTypes::USER_TABLE);

	broadcast_to_peers(peers_message.get_raw(), false);
	broadcast_to_peers(user_table_message.get_raw(), false);

	average = get_average();

	for (i = 0; i < average; i++) {

		update_file_count();

		Globals::file_table.lock();
		top_peer = Globals::file_table.get()->get_top_peer(file_count); //printf("Top peer %x\n",top_peer);
		bottom_peer = Globals::file_table.get()->get_bottom_peer(file_count); //printf("Bottom peer %x\n",bottom_peer);
		file_name = Globals::file_table.get()->get_balancing_file(top_peer,
				bottom_peer); //printf("%s\n",file_name.c_str() );
		Globals::file_table.get()->update_file_table(bottom_peer, top_peer,
				file_name);
		Globals::file_table.unlock();

		message = ByteQueue();
		message.push<std::string>(file_name);
		message.push<unsigned int>(bottom_peer);
		message.push_front<unsigned int>(message.size());
		message.push_front<char>(MessageTypes::RELOCATE_FILE_TO);

		orderQueue.push(Order(top_peer, message.get_raw()));
	}

	send_updated_file_table();

	while (!orderQueue.empty()) {
		send_message(orderQueue.front());
		orderQueue.pop();
	}

}

void Balancer::node_loss(unsigned int lostIpAddr) {

	std::vector<std::string> altered_files;
	unsigned int bottom_peer;
	unsigned int file_location;
	ByteQueue message;


	Globals::file_table.lock();
	Globals::file_table.get()->print_all();
	Globals::file_table.unlock();

	Globals::file_table.lock();
	//printf("Peer perdido: %x\n",lostIpAddr);
	altered_files = Globals::file_table.get()->get_lost_peer_files(lostIpAddr);

	for (auto file_name : altered_files) {

		//printf("Tratando arquivo: %s\n",file_name.c_str());

		update_file_count();
		//print_file_count();
		bottom_peer = Globals::file_table.get()->get_bottom_peer_wo_file(file_count, file_name);
		//printf("Bottom peer w/o file: %x\n\n", bottom_peer);
		file_location = Globals::file_table.get()->get_alternate_file_location(
				file_name, lostIpAddr);
		//printf("\n\nAlternate peer %x\n", file_location);

		Globals::file_table.get()->update_file_table(bottom_peer, lostIpAddr,
				file_name);
		//Globals::file_table.get()->print_all();
		

		message = ByteQueue();
		message.push<std::string>(file_name);
		message.push<unsigned int>(bottom_peer);
		message.push_front<unsigned int>(message.size());
		message.push_front<char>(MessageTypes::SEND_FILE_TO);
		orderQueue.push(Order(file_location, message.get_raw()));
	}
	Globals::file_table.unlock();

	send_updated_file_table();

	while (!orderQueue.empty()) {
		send_message(orderQueue.front());
		orderQueue.pop();
	}

}

void Balancer::file_deletion() {

	ByteQueue message;
	bool gtr, lsr, balanced;
	unsigned int lsr_addr, gtr_addr;
	std::string file_name;
	int average;

	balanced = false;

	while (!balanced) {
		gtr = false;
		lsr = false;

		average = get_average();
		Globals::file_table.lock();

		update_file_count();

		for (auto it = file_count.begin(); it != file_count.end(); it++) {
			if (it->second < average && !lsr) {
				lsr = true;
				lsr_addr = it->first;
			} else if (it->second > average && !gtr) {
				gtr = true;
				gtr_addr = it->first;
			}
		}

		if (!lsr) {
			balanced = true;
		} else {
			file_name = Globals::file_table.get()->get_balancing_file(gtr_addr,
					lsr_addr);
			Globals::file_table.get()->update_file_table(lsr_addr, gtr_addr,
					file_name);

			message = ByteQueue();

			message.push<std::string>(file_name);
			message.push<unsigned int>(lsr_addr);
			message.push_front<unsigned int>(message.size());
			message.push_front<char>(MessageTypes::RELOCATE_FILE_TO);

			orderQueue.push(Order(gtr_addr, message.get_raw()));
		}

		Globals::file_table.unlock();

	}

	send_updated_file_table();

	while (!orderQueue.empty()) {
		send_message(orderQueue.front());
		orderQueue.pop();
	}
}

void Balancer::run(EventType event, unsigned int eventIpAddr) {

	//update_manager_ip();

	//printf("Chegou evento: %d\n", event);

	//update_file_count();

	switch (event) {
	case NEW_NODE:
		new_node();
		break;
	case NODE_LOSS:
		node_loss(eventIpAddr);
		break;
	case FILE_DELETION:
		file_deletion();
		break;
	default:
		break;
	}
}

